package logic;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;

import messages.GameEventDispatcher;
import messages.GameEventListener;
import messages.GameMessage;
import messages.MatchWinner;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import Pieces.Djed;
import Pieces.Obelisk;
import Pieces.Pharaoh;
import Pieces.Pyramid;

public class Board implements GameEventDispatcher{
	private Cell[][] cells;
	private final byte boardDimX = 8;
	private final byte boardDimY = 10;
	private Team userTurn;
	
	private int laserX;
	private int laserY;
	private BeamDirection laserDirection;
	
	private HashMap<String, Integer> xaxisMapping;
	private HashMap<String, Integer> yaxisMapping;
	private HashMap<String, Short> orientationMapping;
	private boolean xaxisMappingDefined = false;
	private boolean yaxisMappingDefined = false;
	private boolean orientationMappingDefined = false;
	
	private File boardConfiguration;
	
	private ArrayList<GameEventListener> listeners;
	
//	public Board(String configurationFile, Team firstPlayer) throws IOException, ParserConfigurationException, SAXException{
//		this(configurationFile);
//		this.userTurn = firstPlayer;
//	}
	
	public void setBoard(Document confDocument) throws ParserConfigurationException, SAXException, IOException{
		confDocument.normalize();
		clearBoard();
		NodeList axisMaps = confDocument.getElementsByTagName("posmapping");
		if(axisMaps.getLength()==0){
			xaxisMappingDefined = false;
			yaxisMappingDefined = false;
		}
		for(int i=0;i<axisMaps.getLength();i++){
			Element mapNode = (Element) axisMaps.item(i);
			if(!mapNode.hasAttributes()) continue;
			String axis = mapNode.getAttribute("axis");
			if(axis.equals("x")){
				xaxisMappingDefined = true;
				xaxisMapping = new HashMap<String, Integer>();
				NodeList mapping = mapNode.getElementsByTagName("map");
				for(int j=0;j<mapping.getLength();j++){
					Element map = (Element)mapping.item(j);
					String mapFrom = map.getAttribute("name");
					Integer mapTo = Integer.parseInt(map.getTextContent());
					xaxisMapping.put(mapFrom, mapTo);
				}
			}else if(axis.equals("y")){
				yaxisMappingDefined = true;
				yaxisMapping = new HashMap<String, Integer>();
				NodeList mapping = mapNode.getElementsByTagName("map");
				for(int j=0;j<mapping.getLength();j++){
					Element map = (Element) mapping.item(j);
					String mapFrom = map.getAttribute("name");
					Integer mapTo = Integer.parseInt(map.getTextContent());
					yaxisMapping.put(mapFrom, mapTo);
				}
			}
		}
		NodeList orientationMaps = confDocument.getElementsByTagName("orientation");
		if(orientationMaps.getLength()==0)
			orientationMappingDefined = false;
		for(int i=0;i<orientationMaps.getLength();i++){
			Element mapNode = (Element) orientationMaps.item(i);
			orientationMappingDefined = true;
			orientationMapping = new HashMap<String, Short>();
			NodeList mapping = mapNode.getElementsByTagName("map");
			for(int j=0;j<mapping.getLength();j++){
				Element map = (Element)mapping.item(j);
				String mapFrom = map.getAttribute("name");
				Short mapTo = Short.parseShort(map.getTextContent());
				orientationMapping.put(mapFrom, mapTo);
			}		
		}
		setPiecesOnBoard(confDocument);
		
		NodeList playerTurn = confDocument.getElementsByTagName("firstPlayer");
		this.userTurn = Team.RED;
		if(playerTurn!=null){
			Element turn = (Element)playerTurn.item(0);
			String player = turn.getAttribute("player");
			if(player==null){
				return;
			}
			if(player.equals("red")){
				return;
			}else if(player.equals("gray")){
				this.userTurn = Team.GRAY;
				return;
			}
		}

	}
	
	public Board(String configurationFile) throws FileNotFoundException, IOException, ParserConfigurationException, SAXException{
		clearBoard();
		boardConfiguration = new File(configurationFile);
		DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
		Document confDocument = builder.parse(boardConfiguration);
		setBoard(confDocument);
	}
	
	public int getBoardDimX(){
		return boardDimX;
	}
	
	public int getBoardDimY(){
		return boardDimY;
	}
	
	public ArrayList<Integer[]> pressLaser(){
		System.out.println("Press laser!");
		ArrayList<Integer[]> trajectory = new ArrayList<Integer[]>();
		int laserComplement = 0;
		switch (userTurn) {
		case RED:
			/*
			 * Red Laser
			 */
			laserDirection = BeamDirection.LEFT;
			laserX = boardDimX-1;
			laserY = 0;
			laserComplement = 1;
			
			break;
		case GRAY:
			/*
			 * Gray Laser
			 */
			laserDirection = BeamDirection.RIGHT;
			laserX = 0;
			laserY = boardDimY-1;
			laserComplement = -1;
			break;
		}
		Cell nextCell;
		boolean doNextCell = true;
		Integer[] start = {laserX+laserComplement, laserY};
		trajectory.add(start);
		do{	
			nextCell = nextLaserTarget();
			Integer[] coordinates = {laserX, laserY};
			trajectory.add(coordinates);
			if(nextCell!=null){
				switch (laserDirection) {
				case UP:
					laserDirection = nextCell.getPiece().reflexBeam(BeamDirection.DOWN);
					break;
				case DOWN:
					laserDirection = nextCell.getPiece().reflexBeam(BeamDirection.UP);
					break;
				case LEFT:
					laserDirection = nextCell.getPiece().reflexBeam(BeamDirection.RIGHT);
					break;
				case RIGHT:
					laserDirection = nextCell.getPiece().reflexBeam(BeamDirection.LEFT);
					break;
				}
				
				switch (laserDirection) {
				case UP:
					laserY--;
					break;
				case DOWN:
					laserY++;
					break;
				case LEFT:
					laserX--;
					break;
				case RIGHT:
					laserX++;
					break;
				case NODIRECTION:
					/*
					 * TODO
					 * Piece must be removed!, hit by the laser
					 */
					Piece thisPiece = nextCell.getPiece();
					if(thisPiece instanceof Obelisk){
						/*TODO
						 * Remove one obelisk mounted or eliminate the piece
						 */
						Obelisk thisObelisk = (Obelisk)thisPiece;
						if(thisObelisk.getMounted()>1){
							thisObelisk.unmount();
						}else{
							nextCell.removePiece();
						}
						return trajectory;
					}
					if(thisPiece instanceof Pharaoh){
						Team winner = null;
						switch (thisPiece.getTeam()) {
						case RED:
							winner = Team.GRAY;
							break;
						case GRAY:
							winner = Team.RED;
							break;
						}
						sendEvent(new MatchWinner(winner, trajectory));
						return null;
					}
					nextCell.removePiece();
					return trajectory;					
				}
			}else{
				doNextCell = false;
			}
		}while(doNextCell);
		/*
		 * TODO
		 * Laser went out of board
		 */
		return trajectory;
	}
	
	private Cell nextLaserTarget(){
		if(laserX<0||laserY<0||laserX>=boardDimX||laserY>=boardDimY)
			return null;
		Cell currentCell = cells[laserX][laserY];
		switch (laserDirection) {
		case UP:
			while(!currentCell.hasPiece()){
				laserY--;
				if(laserY<0){
					return null;
				}
				currentCell = cells[laserX][laserY];
			}
			return currentCell;
		case DOWN:
			while(!currentCell.hasPiece()){
				laserY++;
				if(laserY>=boardDimY){
					return null;
				}
				currentCell = cells[laserX][laserY];
			}
			return currentCell;
		case LEFT:
			while(!currentCell.hasPiece()){
				laserX--;
				if(laserX<0){
					return null;
				}
				currentCell = cells[laserX][laserY];
			}
			return currentCell;
		case RIGHT:
			while(!currentCell.hasPiece()){
				laserX++;
				if(laserX>=boardDimX){
					return null;
				}
				currentCell = cells[laserX][laserY];
			}
			return currentCell;	
		}
		return null;
	}
	
	private void clearBoard(){
		cells = new Cell[boardDimX][boardDimY];
		for(int i=0;i<boardDimX;i++){
			cells[i][0] = new TeamCell(Team.RED);
			cells[i][boardDimY-1] = new TeamCell(Team.GRAY);
		}
		cells[0][1] = new TeamCell(Team.GRAY);
		cells[boardDimX-1][1] = new TeamCell(Team.GRAY);
		cells[0][boardDimY-2] = new TeamCell(Team.RED);
		cells[boardDimX-1][boardDimY-2] = new TeamCell(Team.RED);
		for(int i=1;i<boardDimX-1;i++){
			cells[i][1] = new TeamCell(Team.NOTEAM);
			cells[i][boardDimY-2] = new NormalCell();
		}
		for(int i=0;i<boardDimX;i++){
			for(int j=2;j<boardDimY-2;j++){
				cells[i][j] = new NormalCell();
			}
		}	
	}
	
	public Document getPiecesOnBoard() throws ParserConfigurationException, TransformerException{
        DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
        Document boardStatus = docBuilder.newDocument();
        
        Element pieceConfiguration = boardStatus.createElement("pieceConfiguration");
        boardStatus.appendChild(pieceConfiguration);

        
        for(int i=0;i<boardDimX;i++){
        	for(int j=0;j<boardDimY;j++){
        		Cell thisCell = cells[i][j];
        		if(thisCell.hasPiece()){
        			Element pieceElement = boardStatus.createElement("piece");
        			Piece thisPiece = thisCell.getPiece();
        			if(thisPiece instanceof Pyramid){
        				Pyramid thisPyramid = (Pyramid) thisPiece;
        				pieceElement.setAttribute("type", "pyramid");
        				switch (thisPyramid.getTeam()) {
						case RED:
	        				pieceElement.setAttribute("owner", "red");							
							break;
						case GRAY:
							pieceElement.setAttribute("owner", "grey");							
							break;
						}
        				pieceElement.setAttribute("posx", i+"");
        				pieceElement.setAttribute("posy", j+"");
        				pieceElement.setAttribute("orientation", thisPyramid.getOrientation()+"");	
        			}else if(thisPiece instanceof Djed){
        				Djed thisDjed = (Djed) thisPiece;
        				pieceElement.setAttribute("type", "djed");
        				switch (thisDjed.getTeam()) {
						case RED:
	        				pieceElement.setAttribute("owner", "red");							
							break;
						case GRAY:
							pieceElement.setAttribute("owner", "grey");							
							break;
						}
        				pieceElement.setAttribute("posx", i+"");
        				pieceElement.setAttribute("posy", j+"");
        				pieceElement.setAttribute("orientation", thisDjed.getOrientation()+"");	
        			}else if(thisPiece instanceof Pharaoh){
        				pieceElement.setAttribute("type", "pharaoh");
        				switch (thisPiece.getTeam()) {
						case RED:
	        				pieceElement.setAttribute("owner", "red");							
							break;
						case GRAY:
							pieceElement.setAttribute("owner", "grey");							
							break;
						}
        				pieceElement.setAttribute("posx", i+"");
        				pieceElement.setAttribute("posy", j+"");
        			}else if(thisPiece instanceof Obelisk){
        				Obelisk thisObelisk = (Obelisk) thisPiece;
        				pieceElement.setAttribute("type", "obelisk");
        				switch (thisPiece.getTeam()) {
						case RED:
	        				pieceElement.setAttribute("owner", "red");							
							break;
						case GRAY:
							pieceElement.setAttribute("owner", "grey");							
							break;
						}
        				pieceElement.setAttribute("posx", i+"");
        				pieceElement.setAttribute("posy", j+"");
        				pieceElement.setAttribute("mounted", thisObelisk.getMounted()+"" );
        			}else{
        				System.err.println("Error on cell "+i+","+j);
        			}
        			pieceConfiguration.appendChild(pieceElement);
        		}
        	}
        }
        
        return boardStatus;
	}
	
	private void setPiecesOnBoard(Document confDocument){
		confDocument.normalize();
		Element pieceConf = (Element) confDocument.getElementsByTagName("pieceConfiguration").item(0);
		NodeList pieces = pieceConf.getElementsByTagName("piece");
		for(int i=0;i<pieces.getLength();i++){
			Element piece = (Element) pieces.item(i);
			String name = piece.getAttribute("type");
			Team owner = Team.NOTEAM;
			String owner_xml = piece.getAttribute("owner");
			int posx;
			String posx_xml = piece.getAttribute("posx");
			int posy;
			String posy_xml = piece.getAttribute("posy");
			short orientation = 0;
			String orientation_xml = piece.getAttribute("orientation");
			if(xaxisMappingDefined){
				posx = xaxisMapping.get(posx_xml);
			}else{
				posx = Integer.parseInt(posx_xml);
			}
			if(yaxisMappingDefined){
				posy = yaxisMapping.get(posy_xml);
			}else{
				posy = Integer.parseInt(posy_xml);
			}
			if(orientationMappingDefined && !orientation_xml.equals("")){
				orientation = orientationMapping.get(orientation_xml);
			}else if(!orientation_xml.equals("")){
				orientation = Short.parseShort(orientation_xml);
			}
			if(owner_xml.equals("red")){
				owner = Team.RED;
			}else if(owner_xml.equals("grey")){
				owner = Team.GRAY;
			}else{
				/*TODO
				 * Throw an error
				 */
			}
			
			Piece currentPiece = null;
			if(name.equals("pyramid")){
				currentPiece = new Pyramid(owner, orientation);
			}else if(name.equals("djed")){
				currentPiece = new Djed(owner, orientation);
			}else if(name.equals("obelisk")){
				int mounted = 2;
				if(piece.hasAttribute("mounted")){
					mounted = Integer.parseInt(piece.getAttribute("mounted"));
				}
				currentPiece = new Obelisk(owner);
				((Obelisk)currentPiece).setMounted(mounted);
			}else if(name.equals("pharaoh")){
				currentPiece = new Pharaoh(owner);
			}else{
				/*TODO
				 * Throw an error
				 */
			}
			
			if(!cells[posx][posy].putPiece(currentPiece)){
				/*TODO
				 * Piece cannot be putted on that cell
				 */
				System.err.println("Could't put piece on "+posx+" "+posy);
			}
		}

	}
	
	
	public boolean umountObelisk(int x1, int y1, int x2, int y2, Team player){
		if(x2>=boardDimX||x2<0||y2>=boardDimY||y2<0)
			return false;
		if(!cells[x1][y1].hasPiece())
			return false;
		Piece thisPiece = cells[x1][y1].getPiece();
		if(!(thisPiece instanceof Obelisk)) return false;
		if(thisPiece.getTeam()!=player)
			return false;
		Obelisk thisObelisk = (Obelisk)thisPiece;
		if(thisObelisk.getMounted()==1) return false;
		System.out.println("umount move from "+x1+","+y1+"->"+x2+","+y2);

		if(Math.abs(x1-x2)<=1&&Math.abs(y1-y2)<=1&&(Math.abs(y1-y2)>0||Math.abs(x1-x2)>0)){
			if(cells[x2][y2].hasPiece()){
				Piece otherPiece = cells[x2][y2].getPiece();
				if(otherPiece instanceof Obelisk && otherPiece.getTeam()==thisPiece.getTeam()){
					thisObelisk.unmount();
					((Obelisk)otherPiece).mount(thisObelisk.getMounted());
					return true;
				}
				return false;
			}
			if(cells[x2][y2].cellOwner()!=thisPiece.getTeam()&&cells[x2][y2].cellOwner()!=Team.NOTEAM)
				return false;
			thisObelisk.unmount();
			cells[x2][y2].putPiece(new Obelisk(thisObelisk.getTeam()));
			return true;
		}
		return false;
	}
	
	public boolean movePiece(int x1, int y1, int x2, int y2, Team player){
		if(x2>=boardDimX||x2<0||y2>=boardDimY||y2<0)
			return false;
		if(!cells[x1][y1].hasPiece())
			return false;
		System.out.println("do move from "+x1+","+y1+"->"+x2+","+y2);

		if(Math.abs(x1-x2)<=1&&Math.abs(y1-y2)<=1&&(Math.abs(y1-y2)>0||Math.abs(x1-x2)>0)){
			Piece thisPiece = cells[x1][y1].getPiece();
			if(thisPiece.getTeam()!=userTurn)
				return false;
			if(cells[x2][y2].hasPiece()){
				Piece otherPiece = cells[x2][y2].getPiece();
				if(thisPiece instanceof Djed){
					if(otherPiece instanceof Obelisk || otherPiece instanceof Pyramid){
						/*
						 * Do swapping
						 */
						cells[x1][y1].removePiece();
						cells[x1][y1].putPiece(otherPiece);
						cells[x2][y2].removePiece();
						cells[x2][y2].putPiece(thisPiece);
						return true;
					}
					return false;
				}
				if(otherPiece.getTeam()!=userTurn)
					return false;
				if(thisPiece instanceof Obelisk){
					if(otherPiece instanceof Obelisk && otherPiece.getTeam()==thisPiece.getTeam()){
						cells[x1][y1].removePiece();
						((Obelisk)otherPiece).mount(((Obelisk)thisPiece).getMounted());
						return true;
					}
					return false;
				}
				return false;
			}
			if(cells[x2][y2].cellOwner()!=thisPiece.getTeam()&&cells[x2][y2].cellOwner()!=Team.NOTEAM)
				return false;
			cells[x1][y1].removePiece();
			cells[x2][y2].putPiece(thisPiece);
			return true;
		}
		return false;
	}
	
	public boolean rotatePieceRight(int x, int y, Team player){
		if(x>=boardDimX||y>=boardDimY||x<0||y<0)
			return false;
		if(!cells[x][y].hasPiece())
			return false;
		Piece piece = cells[x][y].getPiece();
		if(piece.getTeam()!=player) 
			return false;
		if(piece instanceof Obelisk || piece instanceof Pharaoh)
			return false;
		piece.rotateRigth();
		return true;
	}
	
	public boolean rotatePieceLeft(int x, int y, Team player){
		if(x>=boardDimX||y>=boardDimY||x<0||y<0)
			return false;
		if(!cells[x][y].hasPiece())
			return false;
		Piece piece = cells[x][y].getPiece();
		if(piece.getTeam()!=player) 
			return false;
		if(piece instanceof Obelisk || piece instanceof Pharaoh)
			return false;
		piece.rotateLeft();
		return true;
	}
	
	public Team getTurn(){
		return userTurn;
	}
	
	public Team passTurn(){
		if(userTurn==Team.RED){
			userTurn = Team.GRAY;
		}else{
			userTurn = Team.RED;
		}
		return userTurn;
	}

	@Override
	public void sendEvent(GameMessage event) {
		for(GameEventListener listener:listeners){
			listener.gameEvent(event);
		}
	}

	@Override
	public void registerGameListener(GameEventListener listener) {
		if(listeners==null)
			listeners = new ArrayList<GameEventListener>();
		listeners.add(listener);
		
	}
}
